$(function()
{

var $canvas = $('canvas');
var canvas = $canvas[0];
var context = canvas.getContext('2d');

if (!context || typeof(requestAnimationFrame) === 'undefined')
{
	$canvas.replaceWith("Can't init canvas. Sorry. No pong for you.");
	return;
}

function GameContactListener()
{
}

GameContactListener.prototype =
{
	/// Called when two fixtures begin to touch.
	BeginContact: function(contact) { },

	/// Called when two fixtures cease to touch.
	EndContact: function(contact) { },

	/// This is called after a contact is updated. This allows you to inspect a
	/// contact before it goes to the solver. If you are careful, you can modify the
	/// contact manifold (e.g. disable contact).
	/// A copy of the old manifold is provided so that you can detect changes.
	/// Note: this is called only for awake bodies.
	/// Note: this is called even when the number of contact points is zero.
	/// Note: this is not called for sensors.
	/// Note: if you set the number of contact points to zero, you will not
	/// get an EndContact callback. However, you may get a BeginContact callback
	/// the next step.
	PreSolve: function(contact, oldManifold)
	{
		var tempBall, other;

		if (contact.m_fixtureA.m_body === ball)
		{
			tempBall = contact.m_fixtureA.m_body;
			other = contact.m_fixtureB.m_body;
		}
		else if (contact.m_fixtureB.m_body === ball)
		{
			tempBall = contact.m_fixtureB.m_body;
			other = contact.m_fixtureA.m_body;
		}
		else
		{
			contact.SetRestitution(0);
			return;
		}

		ballContact = contact;

		if (other === paddle1 || other === paddle2)
		{
			var ballPos = tempBall.GetPosition();
			var paddlePos = other.GetPosition();
			var sub = b2.Vec2.Subtract(ballPos, paddlePos);
			sub.Normalize();

			sub.x *= ballSpeed;
			sub.y *= ballSpeed;

			newBallVel = sub;
			return;
		}
		else if (other === pacman)
		{
			contact.SetEnabled(false);
			resetBall = true;
			p3Score++;
			return;
		}

		contact.SetRestitution(1.01);
	},

	/// This lets you inspect a contact after the solver is finished. This is useful
	/// for inspecting impulses.
	/// Note: the contact manifold does not include time of impact impulses, which can be
	/// arbitrarily large if the sub-step is small. Hence the impulse is provided explicitly
	/// in a separate data structure.
	/// Note: this is only called for contacts that are touching, solid, and awake.
	PostSolve: function(contact, impulse)
	{
	}
};

var worldScale = 0.5;

var ballSize = .10 * worldScale;
var paddleSize = .35 * worldScale;
var pacmanSize = .12 * worldScale;

var ballSpeed = 3;
var paddleSpeed = 2.3;
var pacSpeed = 1.5;

var debugDraw = new CanvasDebugDraw(context);
debugDraw.scale = 120;
debugDraw.SetFlags(b2.Draw.e_shapeBit);

var scaledWidth = canvas.width / debugDraw.scale;
var scaledHeight = canvas.height / debugDraw.scale;

var world = new b2.World(new b2.Vec2(0, 0));
world.SetDebugDraw(debugDraw);
world.SetContactListener(new GameContactListener());

var worldBody = world.CreateBody(new b2.BodyDef());

var lineFixtureDef = new b2.FixtureDef();
lineFixtureDef.shape = new b2.EdgeShape();

lineFixtureDef.shape.Set(new b2.Vec2(0, scaledHeight - .05), new b2.Vec2(scaledWidth, scaledHeight - .05));
worldBody.CreateFixture(lineFixtureDef);

lineFixtureDef.shape.Set(new b2.Vec2(0, .05), new b2.Vec2(scaledWidth, .05));
worldBody.CreateFixture(lineFixtureDef);

var paddleDef = new b2.BodyDef();
paddleDef.type = b2.Body.b2_dynamicBody;
paddleDef.position.Set(.2, (scaledHeight / 2));
paddleDef.fixedRotation = true;

var paddle1 = world.CreateBody(paddleDef);

paddleDef.position.Set(scaledWidth - .2, (scaledHeight / 2));

var paddle2 = world.CreateBody(paddleDef);

var paddleFixtureDef = new b2.FixtureDef();
paddleFixtureDef.shape = new b2.PolygonShape();
paddleFixtureDef.shape.SetAsBox(paddleSize / 3, paddleSize);
paddleFixtureDef.restitution = 1;
paddleFixtureDef.friction = 0;
paddleFixtureDef.density = 100;

paddle1.CreateFixture(paddleFixtureDef);
paddle2.CreateFixture(paddleFixtureDef);

var pjd = new b2.PrismaticJointDef();
pjd.collideConnected = true;

pjd.Initialize(paddle1, worldBody, new b2.Vec2(0, 0), new b2.Vec2(0, 1));
world.CreateJoint(pjd);

pjd.Initialize(paddle2, worldBody, new b2.Vec2(0, 0), new b2.Vec2(0, 1));
world.CreateJoint(pjd);

var newBallVel = null;
var resetBall = true;

paddleDef.position.Set(scaledWidth / 2, scaledHeight / 2);
paddleFixtureDef.shape = new b2.CircleShape();
paddleFixtureDef.shape.m_radius = ballSize;
paddleFixtureDef.bullet = true;
paddleFixtureDef.density = 0.1;

var ball = world.CreateBody(paddleDef);
ball.CreateFixture(paddleFixtureDef);

paddleDef.position.Set(scaledWidth / 2, scaledHeight / 2 - 1.2);
paddleFixtureDef.shape.m_radius = pacmanSize;
paddleFixtureDef.bullet = false;
paddleFixtureDef.density = 100;

var pacman = world.CreateBody(paddleDef);
pacman.CreateFixture(paddleFixtureDef);

var keysPressed = {};

$(document).keydown(function(e)
{
	keysPressed[e.keyCode] = true;
});

$(document).keyup(function(e)
{
	keysPressed[e.keyCode] = false;
});

var pacMouth = Math.PI / 2, pacRotate = Math.PI / 2;
var pauseFrames;
var p1Score = 0, p2Score = 0, p3Score = 0;

var p1Cpu = false, p2Cpu = false, p3Cpu = false;

$('#p1AI').click(function() { p1Cpu = $('#p1AI').is(':checked'); });
$('#p2AI').click(function() { p2Cpu = $('#p2AI').is(':checked'); });
$('#p3AI').click(function() { p3Cpu = $('#p3AI').is(':checked'); });

function performPaddleCpu(paddle)
{
	var paddlePos = paddle.GetPosition();
	var ballPos = ball.GetPosition();
	var ySep = paddlePos.y - ballPos.y;

	if (ySep > 0.04)
		paddle.SetLinearVelocity(new b2.Vec2(0, -paddleSpeed));
	else if (ySep < -0.04)
		paddle.SetLinearVelocity(new b2.Vec2(0, paddleSpeed));
	else
		paddle.SetLinearVelocity(new b2.Vec2(0, 0));
}

function performPacmanCpu(pac)
{
	var pacPos = pac.GetPosition();
	var ballPos = ball.GetPosition();
	var xSub = pacPos.x - ballPos.x;
	var ySub = pacPos.y - ballPos.y;

	if (Math.abs(xSub) > Math.abs(ySub))
	{
		if (xSub > 0)
		{
			pacman.SetLinearVelocity(new b2.Vec2(-pacSpeed, 0));
			pacRotate = -Math.PI;
		}
		else
		{
			pacman.SetLinearVelocity(new b2.Vec2(pacSpeed, 0));
			pacRotate = 0;
		}
	}
	else
	{
		if (ySub > 0)
		{
			pacman.SetLinearVelocity(new b2.Vec2(0, -pacSpeed));
			pacRotate = -Math.PI / 2;
		}
		else
		{
			pacman.SetLinearVelocity(new b2.Vec2(0, pacSpeed));
			pacRotate = Math.PI / 2;
		}
	}
}

function step()
{
	context.strokeStyle = context.fillStyle = 'white';
	context.clearRect(0, 0, canvas.width, canvas.height);

	if (resetBall)
	{
		ball.SetTransform(new b2.Vec2(scaledWidth / 2, scaledHeight / 2), 0);

		var angle = Math.random() - 0.5;

		if (Math.random() < 0.5)
			angle += (Math.PI / 2);
		else
			angle -= (Math.PI / 2);

		ball.SetLinearVelocity(new b2.Vec2(Math.sin(angle) * ballSpeed, Math.cos(angle) * ballSpeed));

		paddle1.SetTransform(new b2.Vec2(.2, (scaledHeight / 2)), 0);
		paddle2.SetTransform(new b2.Vec2(scaledWidth - .2, (scaledHeight / 2)), 0);
		pacman.SetTransform(new b2.Vec2(scaledWidth / 2, scaledHeight / 2 - 1.3), 0);

		pacman.SetLinearVelocity(new b2.Vec2(0, 0));

		pauseFrames = 3 * 60;
		resetBall = false;
	}

	if (pauseFrames > 0)
		--pauseFrames;
	else
	{
		if (!p1Cpu)
		{
			if (keysPressed[81])
				paddle1.SetLinearVelocity(new b2.Vec2(0, -paddleSpeed));
			else if (keysPressed[65])
				paddle1.SetLinearVelocity(new b2.Vec2(0, paddleSpeed));
			else
				paddle1.SetLinearVelocity(new b2.Vec2(0, 0));
		}
		else
			performPaddleCpu(paddle1);

		if (!p2Cpu)
		{
			if (keysPressed[79])
				paddle2.SetLinearVelocity(new b2.Vec2(0, -paddleSpeed));
			else if (keysPressed[76])
				paddle2.SetLinearVelocity(new b2.Vec2(0, paddleSpeed));
			else
				paddle2.SetLinearVelocity(new b2.Vec2(0, 0));
		}
		else
			performPaddleCpu(paddle2);

		if (!p3Cpu)
		{
			if (keysPressed[38])
			{
				pacman.SetLinearVelocity(new b2.Vec2(0, -pacSpeed));
				pacRotate = -Math.PI / 2;
			}
			else if (keysPressed[40])
			{
				pacman.SetLinearVelocity(new b2.Vec2(0, pacSpeed));
				pacRotate = Math.PI / 2;
			}
			else if (keysPressed[37])
			{
				pacman.SetLinearVelocity(new b2.Vec2(-pacSpeed, 0));
				pacRotate = -Math.PI;
			}
			else if (keysPressed[39])
			{
				pacman.SetLinearVelocity(new b2.Vec2(pacSpeed, 0));
				pacRotate = 0;
			}
		}
		else
			performPacmanCpu(pacman);

		world.Step(1 / 60, 3, 8);
	}

	if (newBallVel)
	{
		ball.SetLinearVelocity(newBallVel);
		newBallVel = null;
	}

	var ballPos = ball.GetPosition();

	if (ballPos.x < 0)
	{
		p2Score++;
		resetBall = true;
	}
	else if (ballPos.x > scaledWidth)
	{
		p1Score++;
		resetBall = true;
	}

	context.lineWidth = 12;
	context.setLineDash([24]);

	context.beginPath();
	context.moveTo(canvas.width / 2, 0);
	context.lineTo(canvas.width / 2, canvas.height);
	context.stroke();
	context.closePath();

	context.setLineDash([0]);

	context.beginPath();
	context.moveTo(0, 0);
	context.lineTo(canvas.width, 0);
	context.stroke();
	context.closePath();

	context.beginPath();
	context.moveTo(0, canvas.height);
	context.lineTo(canvas.width, canvas.height);
	context.stroke();
	context.closePath();

	context.lineWidth = 1;

	var p1Pos = paddle1.GetPosition();
	context.fillRect((p1Pos.x - (paddleSize / 3)) * debugDraw.scale, (p1Pos.y - paddleSize) * debugDraw.scale, ((paddleSize / 3) * 2) * debugDraw.scale, (paddleSize * 2) * debugDraw.scale);
	var p2Pos = paddle2.GetPosition();
	context.fillRect((p2Pos.x - (paddleSize / 3)) * debugDraw.scale, (p2Pos.y - paddleSize) * debugDraw.scale, ((paddleSize / 3) * 2) * debugDraw.scale, (paddleSize * 2) * debugDraw.scale);

	context.fillRect((ballPos.x - ballSize) * debugDraw.scale, (ballPos.y - ballSize) * debugDraw.scale, ballSize * 2 * debugDraw.scale, ballSize * 2 * debugDraw.scale);

	context.save();
	context.strokeStyle = context.fillStyle = 'yellow';

	context.beginPath();

	var p3Pos = pacman.GetPosition();

	context.translate(p3Pos.x * debugDraw.scale, p3Pos.y * debugDraw.scale);
	context.rotate(pacRotate);

	if (pacman.GetLinearVelocity().Length() > 0.01)
		pacMouth += 0.4;

	var m = Math.abs(Math.sin(pacMouth));

	context.arc(0, 0, pacmanSize * debugDraw.scale, m, (Math.PI * 2) - m);
	context.lineTo(0, 0);
	context.closePath();
	context.fill();
	context.strokeStyle = 'black';
	context.stroke();
	context.restore();

	if (pauseFrames > 0)
	{
		var leftSeconds = Math.ceil(pauseFrames / 60);

		context.fillStyle = 'red';
		context.font = '128px Verdana';
		var measure = context.measureText(leftSeconds);
		context.fillText(leftSeconds, (canvas.width / 2) - (measure.width / 2), canvas.height / 2 + 48);
	}

	context.font = '16px Verdana';

	context.fillStyle = 'red';
	measure = context.measureText(p1Score);
	context.fillText(p1Score, 0, 18);

	context.fillStyle = 'green';
	measure = context.measureText(p3Score);
	context.fillText(p3Score, (canvas.width / 2) - (measure.width / 2), 18);

	context.fillStyle = 'blue';
	measure = context.measureText(p2Score);
	context.fillText(p2Score, canvas.width - measure.width, 18);

	requestAnimationFrame(step);
}

requestAnimationFrame(step);

});